/***************************************************************************
 *   Copyright (C) 2008 by Eduardo Gurgel Pinho                            *
 *   edgurgel@gmail.com                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
***************************************************************************/

#include "scene.h"

#include <limits>
#include <typeinfo>

#include "light/punctual_light.h"
#include "light/square_light.h"
#include "object/sphere.h"
#include "object/plane.h"
#include "object/cone.h"
#include "object/cylinder.h"
#include "object/disc.h"
#include "object/triangle.h"
#include "object/quadrilateral.h"
#include "object/mesh.h"

namespace Sirat {

namespace Common {

Scene::Scene(const Color& _background,const double _ambient) : background(_background), ambient(_ambient), attenuation(false) ,  a(0) , b(0), c(1.0), optimization(false), optimization_divisions(40), grid(0) { }

Scene::Scene() : background(Color(0.0,0.0,0.0)), ambient(0.5), attenuation(false) ,  a(0) , b(0), c(1.0), optimization(false), optimization_divisions(40), grid(0) { }

Scene::Scene(const Scene& _scene) {
	background = _scene.background;
	ambient = _scene.ambient;
	attenuation = _scene.attenuation;
	a = _scene.a;
	b = _scene.b;
	c = _scene.c;
	
	for(unsigned int i =0;i<_scene.objects.size(); i++) {
		objects.push_back(_scene.objects.at(i)->clone());
	}
		
	for(unsigned int i =0;i<_scene.lights.size();i++) {
		lights.push_back(_scene.lights.at(i));
	}
}

Scene::~Scene() {
	if(grid) delete grid;
}

tuple<Object::Object*,Math::Vector> Scene::nearest_object(const Math::Ray& _ray) {
	std::numeric_limits<double> double_limit;
	const double infinity = double_limit.infinity();
	double distance = infinity;
	Object::Object* nearest_object = 0;
	Math::Vector nearest_point;

	if(optimization == false && limitable_objects.size() > 0) {
		objects.insert(objects.end(), limitable_objects.begin(), limitable_objects.end());
		limitable_objects.clear();
	}

	for(std::vector<Object::Object*>::iterator iter = objects.begin(); iter != objects.end(); iter++) {
		Object::Object* object = (*iter);

		tuple<double,Math::Vector,Object::Object*> test_intersection = object->intersection(_ray);
		double test_distance = test_intersection.get<0>();
		if(test_distance < distance) { /* Searching the nearest */
			nearest_point = test_intersection.get<1>();
			nearest_object = test_intersection.get<2>();
			distance = test_distance;
		}
	}

	if(optimization) {
		tuple<double, Math::Vector, Object::Object*> grid_result = intersect_grid(_ray);
		double test_distance = grid_result.get<0>();

		if(test_distance < distance) {
			distance = test_distance;
			nearest_object = grid_result.get<2>();
			nearest_point = grid_result.get<1>();
		}
	}

	return make_tuple(nearest_object,nearest_point);
}

void Scene::add_objectlist(std::vector<Object::Object*>* _objectlist) {
	for(unsigned int i =0;i<_objectlist->size();i++) {
		add_object((*_objectlist)[i]);
	}
	update_grid();
}

void Scene::add_object(Object::Object* _object) { objects.push_back(_object); update_grid(); }

void Scene::add_lightlist(std::vector<Light::Light*>* _lightlist) {
	for(unsigned int i =0;i<_lightlist->size();i++) {
		add_light((*_lightlist)[i]);
	}
}

void Scene::add_light(Light::Light* _light) { lights.push_back(_light); }

void Scene::remove_object(const unsigned int _index) {
	objects.erase(objects.begin()+_index);
}
	
void Scene::remove_light(const unsigned int _index) {
	lights.erase(lights.begin()+_index);
}

void Scene::set_background(const Color& _background) { background = _background; }

void Scene::set_attenuation(bool _attenuation) { attenuation = _attenuation; }

void Scene::set_attenuation(double _a,double _b,double _c) {
	a = _a;
	b = _b;
	c = _c;
	attenuation = true;
}

void Scene::set_ambient(double _ambient) { ambient = _ambient; }

void Scene::set_optimization(bool _enable, unsigned int _divisions) {
	if(_divisions < 1) {
		optimization = false;
		return;
	}

	optimization = _enable;
	optimization_divisions = _divisions;
	update_grid();
}

unsigned int Scene::get_objects_size() const { return objects.size(); }

unsigned int Scene::get_lights_size() const { return lights.size(); }

Object::Object* Scene::get_object(const unsigned int _index) const { return objects.at(_index); }

Light::Light* Scene::get_light(const unsigned int _index) const { return lights.at(_index); }

Color Scene::get_background() const { return background; }

double Scene::get_attenuation(const double _t) const {
	 if(attenuation) return 1.0/(1.0 + a*_t*_t + b*_t + c); 
	 else return 1.0; 
}

double Scene::get_attenuation_value(const unsigned short _index) const {
	switch(_index) {
		case 1: return a;
		case 2: return b;
		case 3: return c;
		default: exit(2);
	}
}

double Scene::get_ambient() const {
	return ambient;
}

void Scene::update_grid() {
	if(!optimization) return;

	std::vector<Object::Object*>::iterator iter;
	double infinity = std::numeric_limits<double>().infinity();
	Math::Vector min_point(infinity, infinity, infinity);
	Math::Vector max_point(-infinity, -infinity, -infinity);

	if(grid) {
		delete grid;

		min_point = grid_min_point;
		max_point = grid_max_point;
	}

	grid = new Objects_Grid(boost::extents[optimization_divisions][optimization_divisions][optimization_divisions]);

	std::vector<Object::Object*> objects_step1;
	std::vector<Object::Object*> final_objects;

	for(iter = objects.begin(); iter != objects.end(); iter++) {
		Object::Object* object = (*iter);
		
		if(object->is_set()) {
			std::vector<Object::Object*> object_list = object->get_object_list();
			objects_step1.insert(objects_step1.end(), object_list.begin(), object_list.end());
		} else objects_step1.push_back(object);
	}

	for(iter = objects_step1.begin(); iter != objects_step1.end(); iter++) {
		Object::Object* object = (*iter);

		if(object->is_limitable()) {
			Math::Vector object_min = object->get_min_point();
			Math::Vector object_max = object->get_max_point();

			min_point.x = std::min(min_point.x, object_min.x - 0.0001);
			min_point.y = std::min(min_point.y, object_min.y - 0.0001);
			min_point.z = std::min(min_point.z, object_min.z - 0.0001);

			max_point.x = std::max(max_point.x, object_max.x + 0.0001);
			max_point.y = std::max(max_point.y, object_max.y + 0.0001);
			max_point.z = std::max(max_point.z, object_max.z + 0.0001);

			limitable_objects.push_back(object);
		} else final_objects.push_back(object);
	}

	objects = final_objects;

	grid_min_point = min_point;
	grid_max_point = max_point;

	double size_x = (grid_max_point.x - grid_min_point.x)/optimization_divisions;
	double size_y = (grid_max_point.y - grid_min_point.y)/optimization_divisions;
	double size_z = (grid_max_point.z - grid_min_point.z)/optimization_divisions;

	for(iter = limitable_objects.begin(); iter != limitable_objects.end(); iter++) {
		Object::Object* object = (*iter);
		
		Math::Vector object_min = object->get_min_point();
		Math::Vector object_max = object->get_max_point();

		//Calculate where in grid the OBB is
		unsigned int min_x, min_y, min_z;
		unsigned int max_x, max_y, max_z;

		min_x = std::floor((object_min.x - grid_min_point.x)/size_x);
		min_y = std::floor((object_min.y - grid_min_point.y)/size_y);
		min_z = std::floor((object_min.z - grid_min_point.z)/size_z);

		max_x = std::floor((object_max.x - grid_min_point.x)/size_x);
		max_y = std::floor((object_max.y - grid_min_point.y)/size_y);
		max_z = std::floor((object_max.z - grid_min_point.z)/size_z);

		//Insert in grid
		for(unsigned int i = min_x; i <= max_x; i++)
		for(unsigned int j = min_y; j <= max_y; j++)
		for(unsigned int k = min_z; k <= max_z; k++) {
			((*grid)[i][j][k]).push_back(object);
		}
	}
}

tuple<double, Math::Vector, Object::Object*> Scene::intersect_grid(const Math::Ray& _ray) {
	if(grid == 0) update_grid();
	double size_x = (grid_max_point.x - grid_min_point.x)/optimization_divisions;
	double size_y = (grid_max_point.y - grid_min_point.y)/optimization_divisions;
	double size_z = (grid_max_point.z - grid_min_point.z)/optimization_divisions;

	int origin_x, origin_y, origin_z;
	double infinity = std::numeric_limits<double>().infinity();
	const tuple<double, Math::Vector, Object::Object*> infinity_result = make_tuple(infinity, Math::Vector(), (Object::Object*) 0);

	Math::Vector origin = _ray.get_origin();
	Math::Vector direction = _ray.get_direction();

	do {
		origin_x = std::floor((origin.x - grid_min_point.x)/size_x);
		origin_y = std::floor((origin.y - grid_min_point.y)/size_y);
		origin_z = std::floor((origin.z - grid_min_point.z)/size_z);

		if(origin_x < 0 && direction.x <= 0) return infinity_result;
		if(origin_y < 0 && direction.y <= 0) return infinity_result;
		if(origin_z < 0 && direction.z <= 0) return infinity_result;

		if(origin_x >= optimization_divisions && direction.x >= 0) return infinity_result;
		if(origin_y >= optimization_divisions && direction.y >= 0) return infinity_result;
		if(origin_z >= optimization_divisions && direction.z >= 0) return infinity_result;

		double t = infinity;

		if(origin_x < 0) t = std::min(t, (grid_min_point.x - origin.x)/direction.x);
		if(origin_y < 0) t = std::min(t, (grid_min_point.y - origin.y)/direction.y);
		if(origin_z < 0) t = std::min(t, (grid_min_point.z - origin.z)/direction.z);

		if(origin_x >= optimization_divisions) t = std::min(t, -(origin.x - grid_max_point.x)/direction.x);
		if(origin_y >= optimization_divisions) t = std::min(t, -(origin.y - grid_max_point.y)/direction.y);
		if(origin_z >= optimization_divisions) t = std::min(t, -(origin.z - grid_max_point.z)/direction.z);

		if(t < infinity) origin = origin + direction*(t + 0.00000001);
	} while(!inside_grid(origin_x, origin_y, origin_z));

	unsigned int step_x, step_y, step_z;
	double t_max_x, t_max_y, t_max_z;
	double t_delta_x, t_delta_y, t_delta_z;
	
	if(direction.x < 0) {
		step_x = -1;
		t_max_x = -(origin.x - (grid_min_point.x + origin_x*size_x))/direction.x;
		t_delta_x = -size_x/direction.x;
	} else {
		step_x = 1;
		t_max_x = ((grid_min_point.x + (origin_x + 1)*size_x) - origin.x)/direction.x;
		t_delta_x = size_x/direction.x;
	}

	if(direction.y < 0) {
		step_y = -1;
		t_max_y = -(origin.y - (grid_min_point.y + origin_y*size_y))/direction.y;
		t_delta_y = -size_y/direction.y;
	} else {
		step_y = 1;
		t_max_y = ((grid_min_point.y + (origin_y + 1)*size_y) - origin.y)/direction.y;
		t_delta_y = size_y/direction.y;
	}

	if(direction.z < 0) {
		step_z = -1;
		t_max_z = -(origin.z - (grid_min_point.z + origin_z*size_z))/direction.z;
		t_delta_z = -size_z/direction.z;
	} else {
		step_z = 1;
		t_max_z = ((grid_min_point.z + (origin_z + 1)*size_z) - origin.z)/direction.z;
		t_delta_z = size_z/direction.z;
	}

	while(inside_grid(origin_x, origin_y, origin_z)) {
		double t_max = std::min(t_max_x, std::min(t_max_y, t_max_z));

		std::vector<Object::Object*> voxel_objects = (*grid)[origin_x][origin_y][origin_z];
		Math::Ray new_ray(origin, direction);

		if(voxel_objects.size() != 0) {
			double distance = infinity;
			Math::Vector nearest_point;
			Object::Object* nearest_object = 0;

			for(std::vector<Object::Object*>::iterator iter = voxel_objects.begin(); iter != voxel_objects.end(); iter++) {
				Object::Object* object = (*iter);

				tuple<double,Math::Vector,Object::Object*> test_intersection = object->intersection(new_ray);
				double test_distance = test_intersection.get<0>();
				if(test_distance < distance) { /* Searching the nearest */
					nearest_point = test_intersection.get<1>();
					nearest_object = test_intersection.get<2>();
					distance = test_distance;
				}
			}

			if(distance <= t_max + 0.0000001) return make_tuple(_ray.get_origin().distance(nearest_point), nearest_point, nearest_object);
		}

		if(t_max_x < t_max_y) {
			if(t_max_x < t_max_z) { //X min
				origin_x += step_x;
				t_max_x += t_delta_x;
			} else { //Z min
				origin_z += step_z;
				t_max_z += t_delta_z;
			}
		} else {
			if(t_max_y < t_max_z) { //Y min
				origin_y += step_y;
				t_max_y += t_delta_y;
			} else { //Z min
				origin_z += step_z;
				t_max_z += t_delta_z;
			}
		}
	}

	return infinity_result;
}

bool Scene::inside_grid(unsigned int _x, unsigned int _y, unsigned int _z) {
	return (_x >= 0 && _y >= 0 && _z >= 0 && _x < optimization_divisions && _y < optimization_divisions && _z < optimization_divisions);
}

Scene& Scene::operator=(const Scene& _scene) {
	background = _scene.background;
	ambient = _scene.ambient;
	attenuation = _scene.attenuation;
	a = _scene.a;
	b = _scene.b;
	c = _scene.c;
	
	for(unsigned int i =0;i<_scene.objects.size(); i++) {
		objects.push_back(_scene.objects.at(i)->clone());
	}
		
	for(unsigned int i =0;i<_scene.lights.size();i++) {
		lights.push_back(_scene.lights.at(i)->clone());
	}

	return *this;
}

}

}
